package tallinn;

import java.util.Random;
import java.util.Vector;

import processing.core.PApplet;
import processing.core.PImage;
import processing.dxf.*;

import simpleGeom.Bounds2D;
import simpleGeom.Edge;
import simpleGeom.HEFace;
import simpleGeom.HENode;
import simpleGeom.HEdge;
import simpleGeom.HasPos2D;
import simpleGeom.PGraphicsHelper;
import simpleGeom.Tree;
import simpleGeom.Vec2D;
import simpleGui.PCamera3D;

public class TallinnPenrose extends PApplet {
	PCamera3D cam;
	Vector<Triangle> triangles;
	Vector<Triangle> smallTris;
	float goldenRatio = (1 + sqrt(5)) / 2f;
	Vector<HENode> nodes = new Vector<HENode>();
	Vector<HEdge> edges = new Vector<HEdge>();
	Vector<TalinnFace> faces = new Vector<TalinnFace>();
	PImage streetmap;
	boolean doCam=false;
	String output;
	public void setup() {
		size(1000, 600, P3D);
		streetmap = this
				.loadImage("/Users/administrator/Desktop/streetmap2.jpg");
		initTriangles(300);
		fuseNodes();
		if (doCam){
		cam = new PCamera3D(this, 7, 2f);
		}
		

	}
float wallLength=6.4f;
float scale ;
	public void fuseNodes() {
		nodes = new Vector<HENode>();
		for (int i = 0; i < triangles.size(); i++) {
			Triangle tri = triangles.get(i);
			for (int j = 0; j < 3; j++) {
				int index = getIndex(nodes, tri.pts[j]);
				if (index >= 0)
					tri.pts[j] = nodes.get(index);
				else {
					nodes.add(tri.pts[j]);
				}
			}
		}
		for (int i = 0; i < triangles.size(); i++) {
			Triangle tri = triangles.get(i);

			for (int j = 0; j < 2; j++) {
				HENode n1 = tri.pts[2];
				if (j > 0)
					n1 = tri.pts[j - 1];
				HENode n2 = tri.pts[(j)];
				if (n1.getHEdge(n2) == null) {
					HEdge hEdge = new HEdge(n1, n2);
					hEdge.attach();
					edges.add(hEdge);
				}
			}
		}
		HEdge e1 = edges.firstElement();
		 scale = wallLength / e1.getLength();
		for (int i = 0; i < nodes.size(); i++) {
			HENode node = nodes.get(i);
			node.mult(scale);
		}

		faces = new Vector<TalinnFace>();
		for (int i = 0; i < edges.size(); i++) {
			HEdge hEdge = edges.get(i);
			if (hEdge.getFace() == null) {
				TalinnFace face = new TalinnFace();
				face.setHEdge(hEdge);
				if (face.getArea2D() > 0) {
					hEdge.setFaceToAll(face);
					faces.add(face);
				}
			}
			hEdge = hEdge.getSym();
			if (hEdge.getFace() == null) {
				TalinnFace face = new TalinnFace();
				face.setHEdge(hEdge);
				if (face.getArea2D() > 0) {
					hEdge.setFaceToAll(face);
					faces.add(face);
				}
			}
		}
		Bounds2D b2d = new Bounds2D();
		b2d.addAll(nodes);
		println(b2d);
		float sX = streetmap.width / b2d.getDimX();
		float sY = streetmap.height / b2d.getDimY();
		for (int i = 0; i < faces.size(); i++) {
			TalinnFace face = faces.get(i);
			Vec2D v = face.getGravityCenter();
			int col = streetmap.get((int) (v.x * sX + streetmap.width / 2),
					(int) (v.y * sY + streetmap.height / 2));
			if (green(col) < 50) {
				face.type = 1;
				
			}
		}
		for (int i = 0; i < faces.size(); i++) {
			TalinnFace face = faces.get(i);
			face.nFloors = (int) (random(1, 4));
			if (face.type==1){
				face.nFloors=0;
			}

		}
		for (int i = 0; i < faces.size(); i++) {
			TalinnFace face = faces.get(i);
			if (face.nFloors > 0) {
				Vector<HEFace> nbs = face.getNbs();
				int nSolid = 0;
				for (int j = 0; j < nbs.size(); j++) {

					TalinnFace nb = (TalinnFace) nbs.get(j);
					if (nb.nFloors > 0) {
						nSolid++;
					}
				}
				if (nSolid == 4) {
					TalinnFace nb = (TalinnFace) nbs.firstElement();
					nb.nFloors = 0;
				}
			}
		}
	}

	public int getIndex(Vector<HENode> pts, HENode v) {
		for (int i = 0; i < pts.size(); i++) {
			HENode p = pts.get(i);
			if (HENode.equalPosition(v, p, 0.001f)) {
				return i;
			}
		}
		return -1;
	}

	public Vector<Triangle> subdivide(Vector<Triangle> triangles) {
		Vector<Triangle> result = new Vector<Triangle>();

		for (int i = 0; i < triangles.size(); i++) {
			Triangle tri = triangles.get(i);
			if (tri.type == 0) {
				// Subdivide red triangle
				HENode p = getGoldenRatio(tri.a, tri.b, goldenRatio);
				result.add(new Triangle(0, tri.c, p, tri.b));
				result.add(new Triangle(1, p, tri.c, tri.a));
				// P = A + (B - A) / goldenRatio

				// result += [(0, C, P, B), (1, P, C, A)]
			} else {
				// # Subdivide blue triangle
				HENode q = getGoldenRatio(tri.b, tri.a, goldenRatio);
				HENode r = getGoldenRatio(tri.b, tri.c, goldenRatio);
				result.add(new Triangle(1, r, tri.c, tri.a));
				result.add(new Triangle(1, q, r, tri.b));
				result.add(new Triangle(0, r, q, tri.a));
				// result += [(1, R, C, A), (1, Q, R, B), (0, R, Q, A)]
			}
		}
		return result;
	}

	public HENode getGoldenRatio(HENode a, HENode b, float f) {
		Vec2D v = Vec2D.sub(b, a);
		v.div(f);
		return new HENode(Vec2D.add(v, a));
	}

	public void initTriangles(float r) {
		
		smallTris=new  Vector<Triangle>();
		HENode a = new HENode();
		triangles = new Vector<Triangle>();
		for (int i = 0; i < 10; i++) {
			HENode b = new HENode();
			b.x = r * cos((2 * i - 1) * PI / 10f);
			b.y = r * sin((2 * i - 1) * PI / 10f);
			HENode c = new HENode();
			c.x = r * cos((2 * i + 1) * PI / 10f);
			c.y = r * sin((2 * i + 1) * PI / 10f);

			if (i % 2 == 0) {
				HENode t = b;
				b = c;
				c = t;
			}

			triangles.add(new Triangle(0, a, b, c));
		}
		for (int i = 0; i < 5; i++) {
			triangles = subdivide(triangles);
		}
		
		smallTris = new Vector<Triangle>();
		for (int i = 0; i < 10; i++) {
			HENode b = new HENode();
			b.x = r * cos((2 * i - 1) * PI / 10f);
			b.y = r * sin((2 * i - 1) * PI / 10f);
			HENode c = new HENode();
			c.x = r * cos((2 * i + 1) * PI / 10f);
			c.y = r * sin((2 * i + 1) * PI / 10f);

			if (i % 2 == 0) {
				HENode t = b;
				b = c;
				c = t;
			}

			smallTris.add(new Triangle(0, a, b, c));
		}
		for (int i = 0; i < 6; i++) {
			smallTris = subdivide(smallTris);
		}
	}

	public void drawWall(HEdge h1, float z1, float z2) {
		HEdge h0 = h1.getPrev();
		HEdge h2 = h1.getNext();
		Edge e = Vec2D.getOffsetEdge(h0.getOrg(), h0.getDest(), h2.getOrg(),
				h2.getDest(), 0.2f);
		PGraphicsHelper.drawExtrudeQuad(h1.getOrg(), h1.getDest(), e.getP2(),
				e.getP1(), z1, z2, g);
	}
	public HEdge getNextHEdgeStreet(HEdge hEdge){
		HEdge hE=hEdge;
		do{
			
			TalinnFace f=(TalinnFace)hE.getSym().getFace();
			if (f!=null&&f.type!=1)return hE;
			hE=hE.getNextO();
			
		}while(hE!=hEdge);
		return hEdge;
	}
	public HEdge getPrevHEdgeStreet(HEdge hEdge){
		HEdge hE=hEdge.getSym();
		do{
			TalinnFace f=(TalinnFace)hE.getFace();
			if (f!=null&&f.type!=1)return hE.getSym();
			hE=hE.getPrevO();
			
		}while(hE!=hEdge.getSym());
		return hE.getSym();
	}
	public void drawWall1(HEdge h1, float z1, float z2,boolean balcony1,boolean balcony2,boolean windows) {
		float offset=0.2f;
		HEdge h0 = h1.getPrev();
		HEdge h2 = h1.getNext();
		Edge e = Vec2D.getOffsetEdge(h0.getOrg(), h0.getDest(), h2.getOrg(),
				h2.getDest(), offset);
		Vec2D v=h1.getDir();
		float gap=0.02f;
		v.setLength(offset);
		Vec2D v1=Vec2D.add(h1.getOrg(),v);
		Vec2D v2=Vec2D.sub(h1.getDest(),v);
		Vec2D c=h1.getCenter2D();
		v.setLength(gap);
		
		v.setLength(offset);
		v.rot90();
		
		PGraphicsHelper.drawExtrudeQuad(h1.getOrg(), v1, Vec2D.add(v1,v),
				e.getP1(), z1, z2, g);
		
		
		PGraphicsHelper.drawExtrudeQuad(v2, h1.getDest(), e.getP2(),
				Vec2D.add(v2,v), z1, z2, g);
		
		 v=h1.getDir();
		 v.setLength(gap);
		  v1=Vec2D.add(v1,v);
		 Vec2D c1=Vec2D.sub(c,v);
		 Vec2D c2=Vec2D.add(c,v);
		  v2=Vec2D.sub(v2,v);
		drawWallElement(v1,c1,z1,z2,balcony1,windows);
		drawWallElement(c2,v2,z1,z2,balcony2,windows);
		
		
		
	}
	public void drawWindow(HasPos2D v1,HasPos2D v2,float z1,float z2){
		Edge b1=Vec2D.getOffsetEdge(v1, v2, 0.19f);
		Edge b2=Vec2D.getOffsetEdge(v1, v2, 0.20f);
		
		PGraphicsHelper.drawExtrudeQuad(b1.getP1(),
				b1.getP2(), b2.getP2(),
				b2.getP1(), z1, z2, g);
		
	}
	public void drawBalcony(HasPos2D v1,HasPos2D v2,float z){
		Edge bLine=Vec2D.getOffsetEdge(v1, v2, -1);
		Edge bLine0=Vec2D.getOffsetEdge(v1, v2, -0.9f);
		PGraphicsHelper.drawExtrudeQuad(v1, v2, bLine.getP2(),
				bLine.getP1(), z, z+0.2f, g);
		PGraphicsHelper.drawExtrudeQuad(bLine0.getP1(), bLine0.getP2(), bLine.getP2(),
				bLine.getP1(), z, z+1f, g);
		
		Edge b1=Vec2D.getOffsetEdge(v1, bLine0.getP1(), 0.1f);
		Edge b2=Vec2D.getOffsetEdge(v2, bLine0.getP2(), -0.1f);
		
		PGraphicsHelper.drawExtrudeQuad(v1, b1.getP1(), b1.getP2(),
				bLine0.getP1(), z, z+1f, g);
		PGraphicsHelper.drawExtrudeQuad(v2, b2.getP1(), b2.getP2(),
				bLine0.getP2(), z, z+1f, g);
	}
	public void drawStreet(HEdge h1){
		HEdge h0 = getPrevHEdgeStreet(h1.getPrev());
		HEdge h2 = getNextHEdgeStreet(h1.getNext());
		Edge e = Vec2D.getOffsetEdge(h0.getOrg(), h0.getDest(), h2.getOrg(),
				h2.getDest(), 1,1,1);
		PGraphicsHelper.drawExtrudeQuad(h1.getOrg(),h1.getDest(), e.getP2(), e.getP1(), 0f,0.3f, g);
	}
	public void drawWallElement(HasPos2D v1, HasPos2D v2,float z1,float z2,boolean balcony,boolean window){
		Edge e = Vec2D.getOffsetEdge(v1, v2, 0.2f);
		Vec2D v=Vec2D.sub(v2, v1);
		float frame=0.5f;
		v.setLength(frame);
		Vec2D w1=Vec2D.add(v1, v);
		Vec2D w2=Vec2D.sub(v2, v);
		v.setLength(0.2f);
		v.rot90();
		PGraphicsHelper.drawExtrudeQuad(v1, v2, Vec2D.add(v2,v),
				Vec2D.add(v1,v), z1, z1+1, g);
		PGraphicsHelper.drawExtrudeQuad(v1, w1, Vec2D.add(w1,v),
				Vec2D.add(v1,v), z1+1, z2-0.5f, g);
		PGraphicsHelper.drawExtrudeQuad(w2, v2, Vec2D.add(v2,v),
				Vec2D.add(w2,v), z1+1, z2-0.5f, g);
		PGraphicsHelper.drawExtrudeQuad(v1, v2, Vec2D.add(v2,v),
				Vec2D.add(v1,v), z2-0.5f, z2, g);
		if (balcony){
			drawBalcony(w1,w2,z1);
		}
		if (window){
			drawWindow(w1,w2,z1+1,z2-0.5f);
		}
	}
	
	
	boolean export;
	public void keyPressed(){
		if (key=='e'){
			export=true;
		}
	}
	 RawDXF dxf;
	 float gap=0.05f;
	public void draw() {
		// all 
		background(255);
			//cam.setActive(false);
		
		if (!export&&doCam){
			
		cam.apply(g);
		}
		
		lights();
		noStroke();
		
		stroke(0);
		
		if (export){
			//camera();
			dxf = (RawDXF) createGraphics(width, height, DXF,output);
			
		    beginRaw(dxf);
		    camera();
			
			
		}
		pushMatrix();
		translate(0,0,-1f);
		box(1000,1000,2f);
		popMatrix();
		fill(0, 255, 0);
		randomSeed(1);
		Random rnd = new Random(1);
		int nCells = 0;
		float socket=1.3f;
		for (int i = 0; i < faces.size(); i++) {
			
			TalinnFace face = faces.get(i);
			fill(random(200, 255), random(200, 255), 0);

			if (face.type == 1) {
				fill(255);
				HEdge hE = face.getHEdge();
				do {
					TalinnFace nb=(TalinnFace) hE.getSym().getFace();
					if (nb!=null&&nb.type!=1){
						//drawStreet(hE);
					}
					hE = hE.getNext();

				} while (hE != face.getHEdge());
			} else {
				
				if (face.nFloors==0){
					HEdge hE = face.getHEdge();
					do {
						TalinnFace nb=(TalinnFace) hE.getSym().getFace();
						if (nb==null||nb.type==1){
							drawWall(hE, 0, socket-gap);
							drawWall1(hE, socket+gap,socket+ 3-gap,false,false,false);
							drawWall1(hE, socket+gap+3,socket+ 6-gap,false,false,false);
						}
						else {
							//drawWall(hE, 0, 0.2f);	
						}
						hE = hE.getNext();

					} while (hE != face.getHEdge());
				}
				if (face.nFloors==1){
					HEdge hE = face.getHEdge();
					do {
						TalinnFace nb=(TalinnFace) hE.getSym().getFace();
						if (nb==null||nb.type==1){
							drawWall1(hE, 3+socket+gap,socket+ 6-gap,false,false,false);
						}
						else {
							//drawWall(hE, 0, 0.2f);	
						}
						hE = hE.getNext();

					} while (hE != face.getHEdge());
				}
				nCells += face.nFloors;
				HEdge hE = face.getHEdge();
				do {
					TalinnFace nb=(TalinnFace) hE.getSym().getFace();
					
					for (int l = 0; l < face.nFloors; l++) {
						if ( l>0&&(nb==null||l>nb.nFloors)){
							drawWall1(hE, l * 3+socket+gap, (l + 1) * 3+socket-gap,random(1)>0.7f,random(1)>0.7f,true);
						}
					else{
						drawWall1(hE, l * 3+socket+gap, (l + 1) * 3+socket-gap,false,false,true);
					}
						
					}
					hE = hE.getNext();

				} while (hE != face.getHEdge());
				hE = face.getHEdge();
				do {
					TalinnFace nb=(TalinnFace) hE.getSym().getFace();
					if (nb==null||nb.nFloors<face.nFloors){
						drawWall(hE, face.nFloors * 3+socket+gap, (face.nFloors) * 3+1+socket-gap);
					}
					hE = hE.getNext();
				} while (hE != face.getHEdge());


				Vector<HENode> nodes = face.getFaceNodes();
				for (int l = 0; l < face.nFloors; l++) {
					PGraphicsHelper.drawExtrudeP(nodes, (l + 1) * 3+socket, 0.2f, g);
				}
				if (export){
					
					if (face.nFloors==0){
					
						dxf.setLayer(1);
					
						PGraphicsHelper.drawExtrudeP(nodes, 1.3f, 0.1f, g);
					}
					dxf.setLayer(0);
					
				}
				
				PGraphicsHelper.drawExtrudeP(nodes, 0, 1.3f, g);
				
				
			
					
					if (face.nFloors==0){
						if (random(1)>0.8f){
						Vec2D center=face.getGravityCenter();
						Tree tree=new Tree();
						pushMatrix();
						translate(center.x,center.y,1);
						rotateZ(random(PI*2f));
						tree.init(0,0,random(1.5f,2f),3,7);
						
						if (export){
						dxf.setLayer(2);
						}
						tree.display(g);
						if (export){
							dxf.setLayer(3);
							}
						tree.displayLeaves(g,1f,100);
						popMatrix();
						}
						
				}
				
				if (export){
					dxf.setLayer(0);
				}
			}
			
			
		}
		if (export){
		dxf.setLayer(4);
		}
		pushMatrix();
		scale(scale,scale,1);
		for (int j=0;j<smallTris.size();j++){
			Triangle tri=smallTris.get(j);
			PGraphicsHelper.drawExtrudeTri(tri.pts[0],tri.pts[1],tri.pts[2], 0, random(0.01f,0.6f), g);
		}
		popMatrix();
		if (export){
		dxf.setLayer(0);
		}
		if (export){
			
			endRaw();
			export=false;
			dxf=null;
			println("exported");
		}
		
		for (int i = 0; i < edges.size(); i++) {
			HEdge hE = edges.get(i);

			// hE.display(g);
		}
	}

	class Triangle {
		HENode a, b, c;
		HENode[] pts = new HENode[3];

		public Triangle(int type, HENode a, HENode b, HENode c) {
			this.type = type;

			this.a = a;
			this.b = b;
			this.c = c;
			pts = new HENode[] { a, b, c };
		}

		int type;

	}
}
